/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File LinkedNode.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.data.Nodeset;
import il.ac.biu.cs.grossmm.api.data.Property;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.data.Unique;
import il.ac.biu.cs.grossmm.api.data.UpdateType;

import java.util.Set;

/**
 * List-based implementation of in-memory active data node.
 * 
 */
public class LinkedNode<N> implements MemNode<N> {

	LinkedNode<N> next0, prev0; // 4+4=8 bytes

	LinkedNode<N> next1, prev1; // 4+4=8 bytes

	Object[] oldValues; // 14+ bytes

	Object[] curValues; // 14+ bytes

	Object[] newValues; // 14+ bytes

	/**
	 * Array of subnode-sets
	 */
	private LinkedNodeset[] subnodes; // 14+ bytes

	private NodeType<N> nodeType; // 4 bytes

	/**
	 * Update type of the current node
	 */
	UpdateType curUpdType, newUpdType; // 4 bytes

	/**
	 * Refers to the set of subnodes (of the parent node) to which this node
	 * belongs
	 */
	LinkedNodeset parentSet; // 4 bytes

	public LinkedNode(NodeType<N> nt) {
		this(nt, null);
	}

	public LinkedNode(NodeType<N> nt, LinkedNodeset parentSet) {
		this.nodeType = nt;
		this.newUpdType = UpdateType.ADDED;
		int nProps = nt.propertiesNumber();
		int nSets = nt.nodesetsNumber();

		this.parentSet = parentSet;

		if (nProps > 0) {
			oldValues = null;
			curValues = new Object[nProps];
			newValues = new Object[nProps];
		}

		if (nSets > 0) {
			subnodes = new LinkedNodeset[nSets];

			for (int i = 0; i < nSets; i++)
				subnodes[i] = new LinkedNodeset(this, i);
		}
	}

	@SuppressWarnings("unchecked")
	public <P> Root<P> getRoot() {
		return (Root<P>) parentSet.rootNode;
	}

	/**
	 * Shifts epochs
	 */
	public void shift() {
		if (curValues != null)
			if (oldValues == null) {
				// first update, emulate no-change
				System.arraycopy(newValues, 0, curValues, 0, newValues.length);
				oldValues = curValues.clone();

				// at this stage oldValues, curValues and newValues all have the
				// same value, moreover oldValues and curValues is the same
				// array
			} else {
				shiftValues();
			}

		// shift any modified subnodes in subtree rooted at this node
		if (subnodes != null)
			for (LinkedNodeset ns : subnodes)
				ns.shift();

		// shift modification type
		curUpdType = newUpdType;
		newUpdType = UpdateType.UNCHANGED;
	}

	/**
	 * Shift of epochs for a node which did not change between the current and
	 * the new epochs but changed between the old and the current epochs
	 * 
	 */
	public void shift2() {

		shiftValues();

		if (subnodes != null)
			for (LinkedNodeset ns : subnodes)
				ns.shift2();

		curUpdType = UpdateType.UNCHANGED;
	}

	/**
	 * Shifts values of the node
	 */
	private void shiftValues() {
		// all other updates
		// oldValues = curValues.clone();
		// System.arraycopy(newValues, 0, curValues, 0, newValues.length);

		Object[] t = oldValues;
		oldValues = curValues;
		System.arraycopy(newValues, 0, t, 0, newValues.length);
		curValues = t;

		// at this stage curValues and newValues have the same value
		// oldValues refers to a copy of old version of curValues
	}

	@SuppressWarnings("unchecked")
	public <V> void setValue(Property<N, V> pt, V value)
			throws OperationFailedException {

		if (pt instanceof Unique) {
			if (parentSet != null) {
				MemNode parentNode = parentSet.parentNode;
				int parentIndex = parentSet.parentIndex;
				MemNode n2 = parentNode.getSubnode(parentIndex, pt.index(),
						value, 1);
				if (n2 != null && n2 != this)
					throw new OperationFailedException("Constraint violation");
			}
		}

		newValues[pt.index()] = value;

		changed();
	}

	public <V> boolean hasValue(Property<N, V> pt, V value) {
		int index = pt.index();
		Object current = newValues[index];

		if (current == null)
			return value == null;

		if (current == value || current.equals(value))
			return true;

		return false;
	}

	/**
	 * Marks node and all its subnodes removed
	 * 
	 */
	public void remove() {
		if (newUpdType == UpdateType.REMOVED)
			return;

		if (subnodes != null)
			for (int i = 0; i < subnodes.length; i++) {
				subnodes[i].clear();
			}

		if (parentSet != null)
			parentSet.moveTo(this, UpdateType.REMOVED);

		newUpdType = UpdateType.REMOVED;
	}

	public void changed() {
		if (newUpdType == UpdateType.UNCHANGED) {
			if (parentSet != null)
				parentSet.moveTo(this, UpdateType.CHANGED);

			newUpdType = UpdateType.CHANGED;
		}
	}

	@SuppressWarnings("unchecked")
	public <P> MemNode<P> getParent() {
		if (parentSet == null)
			return null;
		return parentSet.parentNode;
	}

	public NodeType<N> getNodeType() {
		return nodeType;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getValue(il.ac.biu.cs.grossmm.api.data.Property,
	 *      int)
	 */
	@SuppressWarnings("unchecked")
	public <V> V getValue(Property<N, V> property, int epoch) {
		return (V) getValue(property.index(), epoch);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getValue(int, int)
	 */
	@SuppressWarnings("unchecked")
	public <V> V getValue(int index, int epoch) {
		switch (epoch) {
		case 0:
			return (V) oldValues[index];
		case 1:
			return (V) curValues[index];
		case 2:
			return (V) newValues[index];
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnodes(il.ac.biu.cs.grossmm.api.data.Nodeset,
	 *      il.ac.biu.cs.grossmm.api.data.UpdateType, int)
	 */
	@SuppressWarnings("unchecked")
	public <S> Set<Node<S>> getSubnodes(Nodeset<N, S> nodeSet, UpdateType u,
			int stage) {
		int index = nodeSet.index();

		LinkedNodeset ns = subnodes[index];

		return new ListSet<S>(ns, u, stage);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnodes(int, int)
	 */
	@SuppressWarnings("unchecked")
	public <S> Set<Node<S>> getSubnodes(int index, int stage) {
		LinkedNodeset subnodes = this.subnodes[index];
		return new MergedListSet<S>(subnodes, stage);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getSubnode(int, int, U,
	 *      int)
	 */
	@SuppressWarnings("unchecked")
	public <M, S, U> MemNode<S> getSubnode(int nIndex, int uIndex, U value,
			int epoch) {
		Set<HashedNode<S>> set = (Set) getSubnodes(nIndex, epoch);

		for (MemNode<S> n : set) {
			Object val = n.getValue(uIndex, epoch + 1);
			if (val == null)
				continue;
			if (val.equals(value))
				return n;
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#createSubnode(il.ac.biu.cs.grossmm.api.data.Nodeset)
	 */
	public <S> Node<S> createSubnode(Nodeset<N, S> nodeSet) {
		NodeType<S> nt = nodeSet.nestedNodeType();

		LinkedNodeset nodes = subnodes[nodeSet.index()];

		LinkedNode<S> newNode = new LinkedNode<S>(nt, nodes);

		nodes.add(newNode);

		return newNode;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see il.ac.biu.cs.grossmm.impl.activeData.MemNode#getUpdateType(int)
	 */
	public UpdateType getUpdateType(int epoch) {
		if (epoch == 0)
			return curUpdType;
		else
			return newUpdType;
	}
}
